using System.Collections.Generic;
using System.ComponentModel;
using System.DataStructures;
using System.Globalization;
using System.IO;

namespace System.Text
{
	/// <summary>
	/// this class implements IDisposable interface and defines string builder functions
	/// </summary>
	internal class DelimitedStringBuilder : IDisposable
	{
		/// <summary>
		/// 
		/// </summary>
		public const bool DefaultAllowNullAppends = false;

		/// <summary>
		/// 
		/// </summary>
		public const string DefaultDelimiter = ",";

		private bool _allowNullAppends;
		private StringBuilder _builder;
		private string _delimiter;
		private SparsePascalSet _delimiterLocations = new SparsePascalSet();
		private bool _disposed;
		private bool _hasContent;

		/// <summary>
		/// Initializes a new instance of the <see cref="DelimitedStringBuilder"/> class.
		/// The default delimiter is ",".
		/// </summary>
		public DelimitedStringBuilder()
			: this(DefaultDelimiter, 256) {}

		/// <summary>
		/// Initializes a new instance of the <see cref="DelimitedStringBuilder"/> class.
		/// </summary>
		/// <param name="delimiter">The delimiter.</param>
		public DelimitedStringBuilder(string delimiter)
			: this(delimiter, 256) {}

		/// <summary>
		/// Initializes a new instance of the <see cref="DelimitedStringBuilder"/> class.
		/// </summary>
		/// <param name="delimiter">The delimiter.</param>
		/// <param name="capacity">The capacity.</param>
		public DelimitedStringBuilder(string delimiter, int capacity)
		{
			_delimiter = delimiter;
			_builder = new StringBuilder(capacity);
		}

		/// <summary>
		/// Gets or sets a value indicating whether [allow null appends].
		/// </summary>
		/// <value><c>true</c> if [allow null appends]; otherwise, <c>false</c>.</value>
		[DefaultValue(DefaultAllowNullAppends)]
		public bool AllowNullAppends
		{
			get { return _allowNullAppends; }
			set { _allowNullAppends = value; }
		}

		/// <summary>
		/// Gets or sets the delimiter.
		/// </summary>
		/// <value>The delimiter.</value>
		public string Delimiter
		{
			get { return _delimiter; }
			set
			{
				if (_delimiterLocations.Count > 0)
				{
					//resize if necessary
					int delimiterLengthDelta = value.Length - _delimiter.Length;
					int newLength = delimiterLengthDelta*_delimiterLocations.Count + _builder.Length;
					if (newLength > _builder.Capacity)
					{
						_builder.EnsureCapacity(newLength + 128);
					}
					//replace & update locations.
					int offset = 0;
					int searchLength = (_delimiter.Length > value.Length) ? _delimiter.Length : value.Length;
					SparsePascalSet newLocations = new SparsePascalSet();
					foreach (int oldLocation in _delimiterLocations)
					{
						int location = oldLocation + offset;
						_builder.Replace(_delimiter, value, location, searchLength);
						newLocations.Add(location);
						offset += delimiterLengthDelta;
					}
					_delimiterLocations = newLocations;
				}
				_delimiter = value;
			}
		}

		/// <summary>
		/// Gets the length.
		/// </summary>
		/// <value>The length.</value>
		public int Length
		{
			get { return _builder.Length; }
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(char[] value)
		{
			if (!_allowNullAppends)
			{
				Guard.NotNull(value, "value");
			}
			AddDelimiter();
			if (value != null)
			{
				_builder.Append(value);
			}
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(object value)
		{
			if (!_allowNullAppends)
			{
				Guard.NotNull(value, "value");
			}
			AddDelimiter();
			if (value != null)
			{
				_builder.Append(value);
			}
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(ulong value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(uint value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(ushort value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(decimal value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(double value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(float value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(long value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(int value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(short value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(char value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(byte value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(sbyte value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">if set to <c>true</c> [value].</param>
		public void Append(bool value)
		{
			AddDelimiter();
			_builder.Append(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void Append(string value)
		{
			if (!_allowNullAppends)
			{
				Guard.NotNull(value, "value");
			}
			AddDelimiter();
			if (value != null)
			{
				_builder.Append(value);
			}
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="repeatCount">The repeat count.</param>
		public void Append(char value, int repeatCount)
		{
			AddDelimiter();
			_builder.Append(value, repeatCount);
		}

		/// <summary>
		/// Appends the each.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="values">The values.</param>
		public void AppendEach<T>(IEnumerable<T> values)
		{
			foreach (T value in values)
			{
				Append(value);
			}
		}

		/// <summary>
		/// Appends the specified value with the given format.
		/// </summary>
		/// <param name="format">The format.</param>
		/// <param name="arg0">The arg0.</param>
		public void AppendFormat(string format, object arg0)
		{
			AddDelimiter();
			_builder.AppendFormat(CultureInfo.CurrentCulture, format, new[] {arg0});
		}

		/// <summary>
		/// Appends the specified values with the given format.
		/// </summary>
		/// <param name="format">The format.</param>
		/// <param name="arg0">The arg0.</param>
		/// <param name="arg1">The arg1.</param>
		public void AppendFormat(string format, object arg0, object arg1)
		{
			AddDelimiter();
			_builder.AppendFormat(CultureInfo.CurrentCulture, format, new[] {arg0, arg1});
		}

		/// <summary>
		/// Appends the specified values with the given format.
		/// </summary>
		/// <param name="format">The format.</param>
		/// <param name="arg0">The arg0.</param>
		/// <param name="arg1">The arg1.</param>
		/// <param name="arg2">The arg2.</param>
		public void AppendFormat(string format, object arg0, object arg1, object arg2)
		{
			AddDelimiter();
			_builder.AppendFormat(CultureInfo.CurrentCulture, format, new[] {arg0, arg1, arg2});
		}

		/// <summary>
		/// Appends the specified values with the given format.
		/// </summary>
		/// <param name="format">The format.</param>
		/// <param name="args">The args.</param>
		public void AppendFormat(string format, params object[] args)
		{
			AddDelimiter();
			_builder.AppendFormat(CultureInfo.CurrentCulture, format, args);
		}

		/// <summary>
		/// Appends the contents of the other builder, merging in the correct locations,
		/// the value of the current delimiter.
		/// </summary>
		/// <param name="other">The other.</param>
		public void AppendMerge(DelimitedStringBuilder other)
		{
			if (!other._hasContent)
			{
				return;
			}

			DelimitedStringBuilder clone = other.Clone();
			clone.Delimiter = _delimiter;

			AddDelimiter();
			int mergeStart = _builder.Length;
			_builder.Append(clone.ToString());
			foreach (int i in clone._delimiterLocations)
			{
				_delimiterLocations.Add(mergeStart + i);
			}
		}

		/// <summary>
		/// Clears this instance.
		/// </summary>
		public void Clear()
		{
			_builder.Remove(0, _builder.Length);
			_delimiterLocations.Clear();
			_hasContent = false;
		}

		/// <summary>
		/// Clones this instance.
		/// </summary>
		/// <returns>cloned <see cref="DelimitedStringBuilder"/></returns>
		public DelimitedStringBuilder Clone()
		{
			DelimitedStringBuilder clone = new DelimitedStringBuilder(_delimiter, _builder.Capacity);
			clone._allowNullAppends = _allowNullAppends;
			clone._builder.Append(_builder.ToString());
			clone._delimiterLocations = _delimiterLocations.Clone();
			clone._hasContent = _hasContent;
			return clone;
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
		/// </returns>
		public override string ToString()
		{
			return _builder.ToString();
		}

		/// <summary>
		/// Virtual implememtation that actually does the cleanup work.
		/// Should be overridden if aditional resources should be managed
		/// by the inheriting class.
		/// </summary>
		/// <param name="isDisposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool isDisposing)
		{
			if (_disposed)
			{
				return;
			}

			if (isDisposing)
			{
				_builder = null;
			}

			_disposed = true;
		}

		/// <summary>
		/// Adds a delimiter to the end of the current buffer.
		/// </summary>
		private void AddDelimiter()
		{
			if (_hasContent)
			{
				_delimiterLocations.Add(_builder.Length);
				_builder.Append(_delimiter);
			}
			_hasContent = true;
		}

		/// <summary>
		/// Loads an instance of the <see cref="DelimitedStringBuilder"/> from the specified stream.
		/// </summary>
		/// <param name="reader">The reader.</param>
		/// <returns>an instance of <see cref="DelimitedStringBuilder"/></returns>
		/// <exception cref="Exception"><c>Exception</c>.</exception>
		public static DelimitedStringBuilder LoadFromStream(BinaryReader reader)
		{
			DelimitedStringBuilder delimitedStringBuilder = new DelimitedStringBuilder();
			delimitedStringBuilder._builder = new StringBuilder(reader.ReadString());
			delimitedStringBuilder._delimiter = reader.ReadString();
			string locations = reader.ReadString();
			string[] splitLocations = locations.Substring(1, locations.Length - 2).Split(','); //trim '{' & '}'
			foreach (string s in splitLocations)
			{
				int result;
				if (int.TryParse(s, out result))
				{
					delimitedStringBuilder._delimiterLocations.Add(result);
				}
				else //we lost a delimiter.... this be bad
				{
					throw new Exception("Error parsing delimiter location: " + s);
				}
			}
			return delimitedStringBuilder;
		}

		/// <summary>
		/// Saves this instance to the specified stream.
		/// </summary>
		/// <param name="delimitedStringBuilder">The delimited string builder.</param>
		/// <param name="writer">The writer.</param>
		public static void SaveToStream(DelimitedStringBuilder delimitedStringBuilder, BinaryWriter writer)
		{
			writer.Write(delimitedStringBuilder._builder.ToString());
			writer.Write(delimitedStringBuilder._delimiter);
			writer.Write(delimitedStringBuilder._delimiterLocations.ToString());
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="values">The values.</param>
		/// <returns>
		/// A <see cref="System.String"/> that represents this instance.
		/// </returns>
		public static string ToString<T>(IEnumerable<T> values)
		{
			return ToString(values, DefaultDelimiter, DefaultAllowNullAppends);
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="values">The values.</param>
		/// <param name="delimiter">The delimiter.</param>
		/// <returns>
		/// A <see cref="System.String"/> that represents this instance.
		/// </returns>
		public static string ToString<T>(IEnumerable<T> values, string delimiter)
		{
			return ToString(values, delimiter, DefaultAllowNullAppends);
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="values">The values.</param>
		/// <param name="allowNulls">if set to <c>true</c> [allow nulls].</param>
		/// <returns>
		/// A <see cref="System.String"/> that represents this instance.
		/// </returns>
		public static string ToString<T>(IEnumerable<T> values, bool allowNulls)
		{
			return ToString(values, DefaultDelimiter, allowNulls);
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="values">The values.</param>
		/// <param name="delimiter">The delimiter.</param>
		/// <param name="allowNulls">if set to <c>true</c> [allow nulls].</param>
		/// <returns>
		/// A <see cref="System.String"/> that represents this instance.
		/// </returns>
		public static string ToString<T>(IEnumerable<T> values, string delimiter, bool allowNulls)
		{
			DelimitedStringBuilder builder = new DelimitedStringBuilder(delimiter);
			builder.AllowNullAppends = allowNulls;
			builder.AppendEach(values);
			return builder.ToString();
		}
	}
}